/*
 * Copyright 2009 Brook Smith brook.smith@paradise.net.nz.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.javagenerate.generator.script;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StreamTokenizer;
import java.util.Properties;

/**
 * Provides basic variable expansion replaces ${string} values with the value
 * from a properties it will also put ${key=value} into the properties
 * for the read back into the properties it also supports expansion so
 * ${somekey=${partial.value}TheRestOftheValue}
 * <b>does not have syntax checking</b> it assumes that the all opening ${ values 
 * will be terminated correctly 
 * 
 * @author Brook
 * 
 */
public class TemplateReader {
	
	static class ProcessState {
		StringBuilder sb = new StringBuilder();
		String last = "";
		int inSequence = 0;
		Properties properties;

		ProcessState(Properties properties) {
			this.properties = properties;
		}

		String sequence(String s) {
			sb.append(s);
			return "";
		}

		void endSequence() {
			inSequence = 0;
			sb.setLength(0);
		}
		
		String process(String current) throws IOException {
			String resString = current;
			
			if (last.equals("$")) { 
				if (current.equals("{")) {
//					System.err.println("current { " + inSequence + ", " + sb.toString());
					if (inSequence == 0) {
						resString = "";
					} else {
						resString = sequence(current);
					}
					inSequence++;
				} else {
					if (inSequence > 0) {
						resString = sequence(last + current);
					} else {
						// print the $
						resString = last+current;
					}
				}
			} else if (inSequence > 0) {
				if (current.equals("}")) {
					if (inSequence > 1) {
						resString = sequence(current);
						inSequence--;
					} else {
						String varName = sb.toString(); 
						if (varName.contains("=")) {
							String[] tokens = varName.split("=");
							// resolve any embedded properties
							ByteArrayInputStream in = new ByteArrayInputStream(tokens[1].getBytes("utf-8"));
							ByteArrayOutputStream out = new ByteArrayOutputStream();
							
							parse(in, out, properties);

							if (tokens[0].equals("inc")) {
								// suck in the resource
								ByteArrayOutputStream out2 = new ByteArrayOutputStream();
								parse(ClassLoader.getSystemResourceAsStream(out.toString("utf-8")), out2, properties);
								resString = out2.toString("utf-8");
							} else {
								properties.setProperty(tokens[0], out.toString("utf-8"));
								resString = "";
							}
						} else {
							resString = properties.getProperty(varName);
						}
						endSequence();
					}
				} else {
					resString = sequence(current);
				}
			} else if (current.equals("$")) {
				resString = "";
			} 
				
			last = current;
			return resString;
		}
	}

	public static void parse(InputStream in, OutputStream out, Properties properties) throws IOException {
		try {

			StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(in)));
			st.ordinaryChars(0, ' ');
			st.ordinaryChar('"');
			st.ordinaryChar('\'');
			st.ordinaryChar('\\');
			st.ordinaryChar('/');
			st.ordinaryChar('.');
			st.ordinaryChars('0', '9');
			ProcessState processState = new ProcessState(properties);
			String s = "";

			while (st.nextToken() != StreamTokenizer.TT_EOF) {
				switch (st.ttype) {
				case StreamTokenizer.TT_EOL:
					s = processState.process("\n");
					break;
				case StreamTokenizer.TT_NUMBER:
					break;
				case StreamTokenizer.TT_WORD:
					s = processState.process(st.sval);
					break;
				default:
					s = processState.process(String.valueOf((char) st.ttype));
				}

				out.write(s.getBytes());
				s = "";
			}

		} finally {
			if (in != null) {
				in.close();
			}
		}
	}

	public static void main(String[] args) {
		Properties properties = new Properties();// {
//			@Override
//			public synchronized Object put(Object arg0, Object arg1) {
//				// you could do some weird arse shit here if you wanted
//				System.err.println("Setting " + arg0 + "=" + arg1);
//				return super.put(arg0, arg1);
//
//			}
//			@Override
//			public String getProperty(String arg0) {
//				String value = super.getProperty(arg0);
//				System.err.println("Getting " + arg0 + "=" + value);
//				return value;
//			}
//		};
		properties.put("somevalue", "this");
		properties.put("some.other.value", "newValue");

		String text = "${base.package=com.javagen.gwt}\n${base.name=Person}\nsome text += '${somevalue}' \"inquotes\", ${full.name=${base.package}.client.Person} \n\t${some.other.value} ${readback=readback.value} { $}\t kjasd.;${base.package}\n${full.name}\n${inc=sample/basic.genscript}";
		System.out.println(text);
		ByteArrayInputStream in = new ByteArrayInputStream(text.getBytes());
		try {
			parse(in, System.out, properties);
			System.out.println();
			System.out.println(properties);
		} catch (Throwable e) {
			e.printStackTrace();
		}
		try {
			parse(ClassLoader.getSystemResourceAsStream("sample/basic.genscript"), System.out, new Properties());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}
